﻿using System;
using Glue.Internals.Nulls;

namespace Glue.Internals
{
    internal partial class ObjectMemberFactory {
        private class NestedPropertyAccessor:IObjectMemberAccessor,IHoldDifferentBaseMemberName
        {
            private readonly CompiledExpression compiledExpression;
            private readonly IObjectMember propertyInfo;
            private readonly INestedObjectCreator nestedObjectCreator;

            public NestedPropertyAccessor(CompiledExpression compiledExpression, 
                IObjectMember propertyInfo, INestedObjectCreator nestedObjectCreator)
            {
                this.compiledExpression = compiledExpression;
                this.propertyInfo = propertyInfo;
                this.nestedObjectCreator = nestedObjectCreator;
            }

            public Maybe InvokeGet(object toInvokeOn)
            {
                var parent = GetParent(toInvokeOn);
                if (!parent.HasValue()) return Maybe.Nothing();
                return propertyInfo.InvokeGet(parent.GetValue()).AsMaybe();
            }

            public void InvokeSet(object toInvokeOn, object toSet)
            {
                var parent = GetParent(toInvokeOn);
                if (!parent.HasValue())
                {
                    nestedObjectCreator.TryCreateAllObjectsInPath(
                        toInvokeOn, compiledExpression.GetEnumeratorForMemberAccessors());
                }
                parent = GetParent(toInvokeOn);
                if (!parent.HasValue()) ThrowParentIsNull();
                propertyInfo.InvokeSet(parent.GetValue(), toSet);
            }


            public string Name
            {
                get 
                {
                    return compiledExpression.CallPath + "." + propertyInfo.Name; 
                }
            }

            public bool CanGetValues
            {
                get { return propertyInfo.CanGetValues; }
            }

            public bool CanSetValues
            {
                get { return propertyInfo.CanSetValues; }
            }

            public Type PropertyType
            {
                get { return propertyInfo.MemberType; }
            }

            public bool CanMapFrom(object thisObject)
            {
                return GetParent(thisObject).HasValue();
            }

            public String BaseMemberName
            {
                get { return compiledExpression.BasePropertyName; }
            }

            private Maybe GetParent(object toInvokeOn)
            {
                return compiledExpression.GetNearestParent(toInvokeOn);
            }

            private void ThrowParentIsNull()
            {
                throw new GlueException("An attempt to set values on the object at '"+compiledExpression.CallPath+"' didn't work, because at least one of the objects is null. Did you forget to instanciate it?" +
                  " You can also try to use the Automagical version of Mapping.Map (the one taking only one parameter)");
            }
        }
    }
}
